contents

1. Java에서 Null 안전(null safety)의 중요성

2. Java의 Optional이란?

기본 사용법

Optional<String> opt = Optional.ofNullable(maybeString);
if (opt.isPresent()) {
    System.out.println(opt.get());
} else {
    System.out.println("값이 없습니다!");
}

3. Optional 객체 생성법

Optional<String> empty = Optional.empty(); // 값 없음
Optional<String> maybe = Optional.of("Hello"); // null 아님
Optional<String> safe = Optional.ofNullable(possibleNullString); // null 혹은 값

4. Optional 주요 메서드 및 활용 패턴

메서드/패턴 용도/설명
isPresent()/isEmpty() 값 존재 확인 (ifPresent() 사용 권장)
ifPresent(Consumer) 값이 있으면 실행 (ex: opt.ifPresent(val -> ...))
get() 값 반환, 값 없으면 예외(NoSuchElementException) 발생, 피해야 함
orElse(T) 값이 있으면 반환/없으면 기본값
orElseGet(Supplier) 값이 있으면 반환/없으면 supplier 함수 실행
orElseThrow(ExceptionSupplier) 값이 있으면 반환/없으면 예외 발생
map(Function) 값 있을 때 변환
flatMap(Function<Optional>) 중첩 Optional을 한 번에 풀 때
filter(Predicate) 조건 만족하는 값만 유지

예시:

Optional<String> value = Optional.ofNullable(getUserName());
String safeName = value.orElse("anonymous");

String upper = value
    .filter(s -> s.length() > 2)
    .map(String::toUpperCase)
    .orElse("DEFAULT");

5. 실전 Optional 활용법

a. 안전한 체인 접근

public Optional<Address> findAddress(User user) {
    return Optional.ofNullable(user)
                   .map(User::getProfile)
                   .map(Profile::getAddress);
}

b. 값이 있을 때 동작

opt.ifPresent(address -> System.out.println(address.getStreet()));

c. 기본값 지정

String name = optionalName.orElse("Default");
String location = optionalAddress.map(Address::getCity).orElse("Seoul");

d. 값 없음 시 예외 던지기

User user = findUser(id).orElseThrow(() -> new NotFoundException("User not found!"));

e. Optional 컬렉션 처리

List<Optional<User>> maybeUsers = ...;
List<User> users = maybeUsers.stream()
    .flatMap(Optional::stream)
    .collect(Collectors.toList());

6. Java에서 Null 안전 처리 (Optional 사용 유무 비교)

전통적 null 처리

if (user != null && user.getProfile() != null) {
    String address = user.getProfile().getAddress();
}

Optional이용 (더 선언적)

String address = Optional.ofNullable(user)
    .map(User::getProfile)
    .map(Profile::getAddress)
    .orElse("No Address");

기타 null 안전 기법

7. Optional 적절 사용 시점

8. Optional 오용 및 반패턴

9. Java의 Null 안전 vs Kotlin

Java에서는 NPE 발생 가능성을 항상 염두에 두고 개발해야 합니다.

10. 전체 활용 예시

public Optional<User> findUserById(int id) {
    return id > 0 ? Optional.of(new User("Alice")) : Optional.empty();
}

// 사용
User user = findUserById(id)
    .filter(User::isActive)
    .orElseThrow(() -> new IllegalArgumentException("No active user"));

11. Optional/Null 안전 주요 패턴 요약

패턴 / 메서드 용도/설명
Optional.of(value) null 불가한 값 래핑 (null이면 예외)
Optional.ofNullable(value) null 가능 값 래핑
Optional.empty() 값 없음
isPresent()/ifPresent() 값 존재 확인/처리
orElse()/orElseGet() 값 없으면 기본/함수형 기본값 반환
orElseThrow() 값 없으면 예외 던짐
map()/flatMap() 존재 시 변환, 체이닝
filter() 조건 걸러내기
전통 null 체크 if (obj != null)
StringUtils 등 유틸 StringUtils.isNotEmpty(str)
값 비교시 안전 equals "val".equals(str)
null 대신 빈 컬렉션 반환 Collections.emptyList() 사용

정리:
Java의 Optional은 모든 null의 대체재는 아니지만, 반환값(특히 "값이 없을 수 있음")에서 null 안전성 향상과 명시적 사용을 지원합니다. Optional과 방어적 코딩, 유틸리티 함수 활용, 혹은 설계 단계에서 null 아예 회피까지 함께 고려하면 코드 품질이 크게 향상됩니다.


1. 핵심 개념 요약 (Concept Summary)

Java (Optional 및 기존 방식) Kotlin (Null-Safety 내장)
설계 Null 허용, 예외(-Optionals) 없으면 NPE 타입 시스템에서 null 안전을 언어 차원에서 보장
사용법 직접 null 체크, Optional로 감쌈 타입에 ? 붙여 nullable 표현 및 연산
컴파일 NPE 문제는 런타임에야 발견 (Optional, 애너테이션, 수작업 체크 필요) 컴파일러가 미사용 시점에서 NPE 가능성 강제 차단
문법 Optional<T> 객체, map/filter/ifPresent/orElse 등 체이닝 T(non-null), T?(nullable), safe call(?.) 등
제공기능 값 래핑/해제, 체이닝, orElse, orElseThrow, map 등 안전 호출, Elvis(?:), not-null assertion(!!), let, filterNotNull 등
성능 Optional은 객체/박싱 오버헤드, 불필요하게 오용될 수 있음 nullable 타입은 별도 객체 생성 없음 (성능/메모리 부담 거의 없음)

2. 구체적 비교

A. Java의 null & Optional

1) null 체크 & NPE

String name = user != null ? user.getName() : "Default";

여전히 간과하면 NPE 가능.

2) Optional 사용

Optional<String> name = Optional.ofNullable(user)
    .map(User::getName);

String actual = name.orElse("Default");

B. Kotlin의 null-safety

1) 타입 시스템에서 null 여부 명확화

var name: String = "hello"    // null불가
var nickname: String? = null  // null 가능
// name = null // 컴파일 오류!

2) 안전 호출 연산자 (Safe call: ?.)

val user: User? = ...
val city: String? = user?.address?.city

3) Elvis 연산자 (?:)

val name: String? = user?.name ?: "Default"

4) let/also 등 고차 함수 활용

user?.let { println(it.name) }

5) 강제 해제 연산자 (!!)

val n = name!! // null이면 즉시 NPE, 사용 권장 X

6) filterNotNull

val nullableList: List<String?> = listOf("a", null, "b")
val onlyNotNull: List<String> = nullableList.filterNotNull()

7) 컴파일러 수준 null 보호

3. 예제 코드 비교

Java

public String safeCity(User user) {
    return Optional.ofNullable(user)
        .map(User::getProfile)
        .map(Profile::getAddress)
        .map(Address::getCity)
        .orElse("Seoul");
}

Kotlin

fun safeCity(user: User?): String =
    user?.profile?.address?.city ?: "Seoul"

4. 설계적 & 실무적 차이

5. 요약 테이블

항목 Java(Optional 포함) Kotlin (nullable 타입 내장)
null 가능 타입 불명확(Optional로 일부 명시) 타입 선언 자체로 명시 (T? or T)
컴파일 단계 체크 일부(애너테이션/Optional 등) 컴파일러가 강제
null 안전 연산자 없음(Optional 체이닝/map/filter) ?., ?:, !!, let, filterNotNull
null 허용 필드 모든 참조형 변수 ? 붙여야만 수용 가능
성능 Optional 오버헤드 有 오버헤드 거의 無 (박싱 없음)

6. 결론 및 실전 적용

references